﻿using System;
using System.Collections.Generic;
using System.Linq;
using AcademicPlanner.Data;

namespace AcademicPlanner.Domain
{
	/// <summary>
	/// ModelCollection(TModel, TEntityRepo, TREntity) is the base for collections
	/// of <c>Model</c> objects.
	/// </summary>
	/// <typeparam name="TModel">The type of the <c>Model</c>s of this collection.</typeparam>
	/// <typeparam name="TEntityRepo">
	/// The type of the <c>EntityRepository(TEntity)</c> of this collection's <c>Model</c>s' underlying <c>Entity</c>
	/// </typeparam>
	/// <typeparam name="TREntity">The type of this collection's <c>Model</c>s' underlying <c>Entity</c></typeparam>
	/// <version>1.0</version>
	/// <since>1.0</since>
	public abstract class ModelCollection<TModel, TEntityRepo, TREntity>
		where TModel : Model
		where TREntity : Entity
		where TEntityRepo : EntityRepository<TREntity>
    {
		/// <summary>
		/// The underlying <c>Entity</c>s of this collection's <c>Model</c>s.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		protected IList<TREntity> entities;


		/// <summary>
		/// Gets the <c>TEntityRepo</c> of the underlying <c>Entity</c>s of this collection's <c>Model</c>s.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		protected abstract TEntityRepo EntityRepo { get; }

		/// <summary>
		/// Gets the <c>DomainMaster</c> of the AcademicPlanner application.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		protected DomainMaster DomainMaster { get { return DomainMaster.GetInstance(); } }


		/// <summary>
		/// No argument constructor; does nothing.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public ModelCollection()
		{

		}

		/// <summary>
		/// Constructs a ModelCollection object and sets its <c>entities</c> to the given
		/// list of <c>TREntity</c>s.
		/// </summary>
		/// <param name="pEntities">The list of <c>TREntity</c>s to initialize this ModelCollection with.</param>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public ModelCollection(IList<TREntity> pEntities)
		{
			entities = pEntities;
		}


		/// <summary>
		/// Converts this ModelCollection to an <c>IList</c> of <c>TModel</c>s.
		/// This method basically creates the <c>Model</c>s of each of its <c>TREntity</c>s.
		/// </summary>
		/// <returns>The list of <c>TModel</c>s.</returns>
		/// <seealso cref="P:DomainMaster.ModelsFountain"/>
		/// <seealso cref="M:ModelsFountain.GetModel<TModel>"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public IList<TModel> ToList()
		{
			IList<TModel> rList = new List<TModel>();

			if (entities != null && entities.Count() > 0)
			{
				foreach (var vEnt in entities)
				{
					rList.Add(DomainMaster.ModelsFountain.GetModel<TModel>(vEnt));
				}
			}

			return rList;
		}

		/// <summary>
		/// Filters this ModelCollection to return only the <c>Model</c> with the given
		/// <c>Entity</c> identifier. This method relies on this ModelCollection's 
		/// <see cref="P:EntityRepo"/> to find the matching <c>Entity</c> then creates
		/// the <c>Model</c> represented by that <c>Entity</c>.
		/// </summary>
		/// <param name="pEntityId">The identifier whose matching <c>Model</c>to return.</param>
		/// <returns>The <c>Model</c> matching the given identifier or <c>null</c>.</returns>
		/// <exception cref="T:CriticalDomainException">
		/// Thrown with an error code of <c>DomainErrorCode.M_ENTITY_RETRIEVAL_BY_ID_FAILURE</c>
		/// when a <see cref="T:AcademicPlanner.Data.DataException"/> occurs;
		/// the <see cref="T:AcademicPlanner.Data.DataException"/> is accessible through the
		/// <c>InnerException</c> property.
		/// </exception>
		/// <seealso cref="P:DomainMaster.ModelsFountain"/>
		/// <seealso cref="M:ModelsFountain.GetModel<TModel>"/>
		/// <seealso cref="P:EntityRepo"/>
		/// <seealso cref="M:AcademicPlanner.Data.EntityRepository<TEntity>.FindById"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public TModel WithId(int pEntityId)
		{
			TModel rMod = null;
			TREntity vEnt = null;
			
			// TODO: QUERY THE EXISTING ENTITIES IF THEY ARE NOT NULL OR USE NHIBERNATE'S SESSION.FILTER()
			try
			{
				vEnt = EntityRepo.FindById(pEntityId);
			}
			catch (DataException vDx)
			{
				var vCx = new CriticalDomainException(DomainErrorCode.M_ENTITY_RETRIEVAL_BY_ID_FAILURE, vDx);
				throw vCx;
			}
			
			if (vEnt != null)
				rMod = DomainMaster.ModelsFountain.GetModel<TModel>(vEnt);

			return rMod;
		}
    }
}
